import requests
from bs4 import BeautifulSoup
import re
import os
import time
import pymysql
from concurrent.futures import ThreadPoolExecutor, as_completed

#边插入边合成音频
class BiQuGeAPI:
    def __init__(self):
        self.base_url = "http://www.ibiquw.info"
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
        }
        self.audio_url = "http://192.168.187.166:9880"
        self.audio_dir = r"D:\pythonworkspace\xiaozhi\xiaozhi-esp32-server-1.0\main\xiaozhi-server\book_audio"
        self.payload = {
            "refer_wav_path": "/data1/github/GPT-SoVITS/wav/dongbeihuack.wav",
            "prompt_text": "你拨打的用户在5秒钟之前已经将手机摔到588米开外了，要想让机主接通电话。",
            "prompt_language": "zh",
            "text_language": "zh"
        }
        self.db_config = {
            "host": "localhost",
            "port": 3308,
            "user": "root",
            "password": "123456",
            "database": "test",
            "charset": "utf8mb4",
        }
        self.executor = ThreadPoolExecutor(max_workers=5)

    def _get_db_connection(self):
        try:
            return pymysql.connect(**self.db_config)
        except pymysql.Error as err:
            print(f"数据库连接失败: {err}")
            return None

    def search_book(self, keyword):
        """线上搜索书籍并返回第一个匹配的书籍ID"""
        url = f"{self.base_url}/modules/article/search.php?searchkey={keyword}"
        try:
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()
            soup = BeautifulSoup(response.text, 'html.parser')
            first_li = soup.find('li')
            first_a = first_li.find('a') if first_li else None
            if first_a:
                href = first_a.get('href')
                match = re.search(r'book/(\d+)/', href)
                if match:
                    return match.group(1)
            print("未找到书籍ID")
        except Exception as e:
            print(f"搜索异常: {e}")
        return None

    def search_book_database(self, keyword, limit):
        """数据库模糊匹配书名，返回章节列表或None"""
        conn = self._get_db_connection()
        if not conn:
            return None
        try:
            with conn.cursor(pymysql.cursors.DictCursor) as cursor:
                sql = """
                    SELECT title, content, platform, book_id 
                    FROM novel_chapters 
                    WHERE name LIKE %s 
                    ORDER BY id ASC
                    LIMIT %s
                """
                cursor.execute(sql, (keyword, limit))
                results = cursor.fetchall()
                if results:
                    print(f"✅ 数据库中已找到《{keyword}》，共 {len(results)} 章")
                    return results
                else:
                    print(f"❌ 数据库中未找到《{keyword}》")
                    return None
        except Exception as e:
            print(f"数据库查询失败: {e}")
            return None
        finally:
            conn.close()

    def split_content_by_length(self, text, max_length=200):
        """把长文本按max_length切分"""
        return [text[i:i + max_length] for i in range(0, len(text), max_length)]

    def synthetic_audio(self, text, filename):
        """同步合成单段音频并保存"""
        self.payload["text"] = text
        os.makedirs(self.audio_dir, exist_ok=True)
        save_path = os.path.join(self.audio_dir, filename)
        try:
            response = requests.post(self.audio_url, json=self.payload, stream=True)
            response.raise_for_status()
            if response.status_code == 200 and response.headers.get("Content-Type") == "audio/wav":
                with open(save_path, "wb") as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        f.write(chunk)
                print(f"✅ 音频保存: {save_path}")
                return save_path
            else:
                print(f"❌ 合成失败，状态码: {response.status_code}，Content-Type: {response.headers.get('Content-Type')}")
        except Exception as e:
            print(f"❌ 合成异常: {e}")
        return None

    def async_synthesize(self, segments, start_index=1):
        """异步合成多个段落音频"""
        for idx, segment in enumerate(segments, start=start_index):
            filename = f"第{idx}段.wav"
            self.synthetic_audio(segment, filename)

    def save_chapter_and_synthesize_audio(self, title, content, book_name, platform, book_id):
        """入库并合成章节音频（前5段同步，剩余异步）"""
        conn = self._get_db_connection()
        if not conn:
            print(f"数据库连接失败，章节'{title}'跳过")
            return
        try:
            with conn.cursor() as cursor:
                insert_sql = """
                INSERT INTO novel_chapters (title, content, name, platform, book_id)
                VALUES (%s, %s, %s, %s, %s)
                ON DUPLICATE KEY UPDATE content = VALUES(content)
                """
                cursor.execute(insert_sql, (title, content, book_name, platform, book_id))
                conn.commit()
                print(f"[入库成功] 章节 '{title}'")

            segments = self.split_content_by_length(content, max_length=200)

            # 前5段同步合成
            front_segments = segments[:5]
            for idx, segment in enumerate(front_segments):
                filename = f"{title}-第{idx + 1}段.wav"
                self.synthetic_audio(segment, filename)

            # 其余异步合成
            if len(segments) > 5:
                rest_segments = segments[5:]
                self.executor.submit(self.async_synthesize, rest_segments, 6)

        except Exception as e:
            print(f"章节 '{title}' 处理异常: {e}")
        finally:
            conn.close()

    def fetch_and_save_and_synthesize(self, book_id, book_name, platform):
        """抓取书籍所有章节，入库并合成音频（异步多线程执行）"""
        try:
            response = requests.get(f"{self.base_url}/book/{book_id}/", headers=self.headers)
            response.encoding = response.apparent_encoding
            soup = BeautifulSoup(response.text, 'lxml')
            book_list = soup.find('div', class_='book_list').find_all('a')
        except Exception as e:
            print(f"抓取目录失败: {e}")
            return

        futures = []
        for book in book_list:
            title = book.text.strip()
            book_url = f"{self.base_url}/book/{book_id}/{book['href']}"
            try:
                chapter_resp = requests.get(book_url, headers=self.headers)
                chapter_resp.encoding = chapter_resp.apparent_encoding
                soup_chapter = BeautifulSoup(chapter_resp.text, 'lxml')
                content = soup_chapter.find('div', id='htmlContent').text.strip()
            except Exception as e:
                print(f"抓取章节内容失败 '{title}': {e}")
                continue

            future = self.executor.submit(
                self.save_chapter_and_synthesize_audio,
                title, content, book_name, platform, book_id
            )
            futures.append(future)
            time.sleep(0.5)

        for future in as_completed(futures):
            try:
                future.result()
            except Exception as e:
                print(f"任务执行异常: {e}")

def main(book_name):
    api = BiQuGeAPI()

    # 先从数据库查询章节，最多查5章测试
    book_data = api.search_book_database(f"%{book_name}%", 5)
    if not book_data:
        print("数据库无缓存，线上搜索并抓取")
        book_id = api.search_book(book_name)
        if book_id:
            api.fetch_and_save_and_synthesize(book_id, book_name, '笔趣阁')
        else:
            print("未找到匹配的书籍ID")
    else:
        print(f"数据库已有数据，开始合成音频...")
        for chapter in book_data:
            title = chapter['title']
            content = chapter['content']
            segments = api.split_content_by_length(content)
            # 同步合成前5段
            for idx, segment in enumerate(segments[:5]):
                filename = f"{title}-第{idx + 1}段.wav"
                api.synthetic_audio(segment, filename)
            # 剩余段落异步合成
            if len(segments) > 5:
                rest_segments = segments[5:]
                api.executor.submit(api.async_synthesize, rest_segments, 6)

if __name__ == "__main__":
    main('星辰变')
